/*
 * NIST Healthcare Test Framework
 * Environment.java Jun 1, 2009
 *
 * This code was produced by the National Institute of Standards and
 * Technology (NIST). See the "nist.disclaimer" file given in the distribution
 * for information on the use and redistribution of this software.
 */
package gov.nist.healthcare.testframework;

import gov.nist.healthcare.configuration.testframework.commons.Host;
import gov.nist.healthcare.configuration.testframework.commons.NameV2Attributes;
import gov.nist.healthcare.configuration.testframework.commons.NameV3Attributes;
import gov.nist.healthcare.configuration.testframework.commons.PropertyType;
import gov.nist.healthcare.configuration.testframework.commons.Actor.SecureHost;
import gov.nist.healthcare.configuration.testframework.testagent.TestAgentConfigurationDocument;
import gov.nist.healthcare.configuration.testframework.testagent.TestAgentConfigurationDocument.TestAgentConfiguration;
import gov.nist.healthcare.configuration.testframework.testagent.TestAgentConfigurationDocument.TestAgentConfiguration.Init;
import gov.nist.healthcare.core.datatypes.v2.HD;
import gov.nist.healthcare.core.message.Name;
import gov.nist.healthcare.core.message.v2.HL7V2Name;
import gov.nist.healthcare.core.message.v3.HL7V3Name;
import gov.nist.healthcare.testframework.application.TestAgent;
import gov.nist.healthcare.testframework.event.EventModel;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * This class defines an environment in the test framework. It contains all the
 * test agents, the addressing information.
 * 
 * @author Sydney Henrard (NIST)
 */
public final class Environment {
    /* Contains the only instance */
    private static Environment instance;

    /* Contains the mapping for the test agents */
    private final Map<Name, TestAgent> testAgents;

    /* Contains the mapping for the security test agents */
    // private final Map<Name, Security> securityTestAgents;

    /* Contains the mapping for actors */
    private final Map<Name, Actor> actors;

    /* Contains the profiles mapping */
    // private final Map<Name, ArrayList<ProfileWrapper>> profiles;

    /* Contains the mapping for security protocols */
    // private final Map<MessageEncoding, Security> securities;

    /* Contains the mapping between a test agent and actors */
    private final Map<Name, List<Name>> testAgentActors;

    /* Contains the event model */
    private final EventModel events;

    /**
     * Private Construtor. Exists only to defeat instantiation.
     */
    private Environment() {
        testAgents = new HashMap<Name, TestAgent>();
        actors = new HashMap<Name, Actor>();
        testAgentActors = new HashMap<Name, List<Name>>();
        events = new EventModel();
    }

    /**
     * Gets the instance of the Environment.
     * 
     * @return the only existing instance
     */
    public static Environment getInstance() {
        if (instance == null) {
            synchronized (Environment.class) {
                instance = new Environment();
            }
        }
        return instance;
    }

    /**
     * Reset the environment
     */
    public void reset() {
        instance = null;
    }

    /**
     * Adds a test agent by using a configuration file.
     * 
     * @param testAgentDocument
     *        the configuration file
     * @return the test agent
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws ClassNotFoundException
     */
    public TestAgent addTestAgent(
            TestAgentConfigurationDocument testAgentDocument)
            throws ClassNotFoundException, InstantiationException,
            IllegalAccessException {
        Map<String, String> params = new HashMap<String, String>();
        Init init = testAgentDocument.getTestAgentConfiguration().getInit();
        if (init != null) {
            List<PropertyType> properties = init.getPropertyList();
            for (PropertyType property : properties) {
                params.put(property.getName(), property.getStringValue());
            }
        }
        return addTestAgent(testAgentDocument, params);
    }

    /**
     * Adds a test agent by using a configuration file.
     * 
     * @param testAgentDocument
     *        the configuration file
     * @param params
     *        a Map containing the parameters for the init method
     * @return the test agent
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws ClassNotFoundException
     */
    public TestAgent addTestAgent(
            TestAgentConfigurationDocument testAgentDocument,
            Map<String, String> params) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException {
        TestAgent testAgent = null;
        if (testAgentDocument.validate()) {
            TestAgentConfiguration config = testAgentDocument.getTestAgentConfiguration();
            // Test Agent
            gov.nist.healthcare.configuration.testframework.commons.TestAgent ta = config.getTestAgent();
            // V2
            Name testAgentName = getV2Name(ta);
            if (testAgentName == null) {
                // V3
                testAgentName = getV3Name(ta);
                // if (testAgentName != null) {
                // // Create a logger for the V3 test agent
                // events.addObserver(new
                // gov.nist.healthcare.testFramework.logging.TestAgentLogger(
                // ((HL7V3Name) testAgentName).getApplicationName()));
                // }
                // } else {
                // // Create a logger for the V2 test agent
                // events.addObserver(new
                // gov.nist.healthcare.testFramework.logging.TestAgentLogger(
                // ((HL7V2Name)
                // testAgentName).getApplicationName().getNamespaceId()));
            }
            if (testAgentName != null) {
                if (!testAgents.containsKey(testAgentName)) {
                    // Create a logger
                    // events.addObserver(new
                    // gov.nist.healthcare.testFramework.logging.TestAgentLogger(
                    // testAgentName));
                    testAgent = getTestAgent(ta.getType());
                    // testAgent = getApplication(ta.getType());
                    if (testAgent == null) {
                        // Environment.getInstance().getEventModel().error(
                        // this.getClass().getCanonicalName(),
                        // "addTestAgent",
                        // "Could not create test agent, bad or unrecognized test agent type: "
                        // + ta.getType());
                        return null;
                    }
                    testAgent.setName(testAgentName);
                    // testAgent.setEval(evaluation);
                    // Initialize the test agent
                    boolean initTestAgent = testAgent.init(params);
                    // if (!initTestAgent) {
                    // Environment.getInstance().getEventModel().error(
                    // this.getClass().getCanonicalName(),
                    // "addTestAgent",
                    // "Could not initialize test agent");
                    // return null;
                    // }
                    // Specific Config
                    if (config.getSpecificConfiguration() != null) {
                        if (!testAgent.loadSpecificConfiguration(config.getSpecificConfiguration())) {
                            // Environment.getInstance().getEventModel().error(
                            // this.getClass().getCanonicalName(),
                            // "addTestAgent",
                            // "Could not load the specialized configuration");
                            return null;
                        }
                    }
                    // Add the test agent to the map
                    testAgents.put(testAgentName, testAgent);
                } else {
                    // Environment.getInstance().getEventModel().error(
                    // this.getClass().getCanonicalName(),
                    // "addTestAgent",
                    // "Could not add the test agent due to name conflict: "
                    // + testAgentName);
                    return null;
                }
            }
            // Actors
            if (config.getActors() != null) {
                List<Name> listTestAgentActors = testAgentActors.get(testAgentName);
                if (listTestAgentActors == null) {
                    listTestAgentActors = new ArrayList<Name>();
                    testAgentActors.put(testAgentName, listTestAgentActors);
                }
                Iterator<gov.nist.healthcare.configuration.testframework.commons.Actor> itA = config.getActors().getActorList().iterator();
                while (itA.hasNext()) {
                    gov.nist.healthcare.configuration.testframework.commons.Actor actorConfig = itA.next();
                    addActor(actorConfig, testAgent, true);
                }
            }
        } else {
            // Environment.getInstance().getEventModel().error(
            // this.getClass().getCanonicalName(), "addTestAgent",
            // "The test agent configuration file is not valid");
            return null;
        }

        return testAgent;
    }

    /**
     * Remove a test agent and all its related information.
     * 
     * @param name
     *        the test agent name
     */
    public void removeTestAgent(Name name) {
        TestAgent testAgent = testAgents.get(name);
        testAgent = null;
        testAgents.remove(name);
        List<Name> actors = testAgentActors.get(name);
        actors = null;
        testAgentActors.remove(name);
    }

    /**
     * Gets the test agent given by the Name.
     * 
     * @param name
     *        the name of the test agent
     * @return the test agent; null if the name does not match with any
     *         registered test agents
     */
    public TestAgent getTestAgent(Name name) {
        TestAgent testAgent = null;
        if (testAgents.containsKey(name)) {
            testAgent = testAgents.get(name);
        }
        return testAgent;
    }

    /**
     * Gets the actor given by the Name.
     * 
     * @param name
     *        the name of the actor
     * @return the actor; null if the name does not match with any registered
     *         actors
     */
    public Actor getActor(Name name) {
        Actor actor = null;
        if (actors.containsKey(name)) {
            actor = actors.get(name);
        }
        return actor;
    }

    /**
     * Gets the actor given by the Name associated to a test agent.
     * 
     * @param actorName
     *        the name of the actor
     * @param testAgentName
     *        the name of the test agent
     * @return the actor; null if the name does not match with any registered
     *         actors
     */
    public Actor getActor(Name actorName, Name testAgentName) {
        Actor actor = null;
        List<Name> actors = getAllActorsName(testAgentName);
        if (actors != null) {
            for (Name name : actors) {
                if (name.equals(actorName)) {
                    actor = getActor(name);
                }
            }
        }
        return actor;
    }

    /**
     * Get all the actors name for a test agent
     * 
     * @param testAgentName
     *        the name of the test agent
     * @return a list of actor name
     */
    public List<Name> getAllActorsName(Name testAgentName) {
        return testAgentActors.get(testAgentName);
    }

    /**
     * Create an instance of the test agent.
     * 
     * @param className
     *        the qualified class name of the test agent
     * @return the test agent
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public TestAgent getTestAgent(String className)
            throws ClassNotFoundException, InstantiationException,
            IllegalAccessException {
        TestAgent ta = null;
        Object o = Class.forName(className).newInstance();
        if (o instanceof TestAgent) {
            ta = (TestAgent) o;
        }
        return ta;
    }

    /**
     * Returns the V2 name of the test agent
     * 
     * @param testAgent
     *        am XmlBeans test agent object
     * @return the V2name
     */
    private Name getV2Name(
            gov.nist.healthcare.configuration.testframework.commons.TestAgent testAgent) {
        Name name = null;
        if (testAgent.getName().getHL7V2ApplicationName() != null) {
            NameV2Attributes app = testAgent.getName().getHL7V2ApplicationName();
            NameV2Attributes fac = testAgent.getName().getHL7V2FacilityName();
            name = new HL7V2Name(new HD(app.getNsId(), app.getUid(),
                    app.getUidType()), new HD(fac.getNsId(), fac.getUid(),
                    fac.getUidType()));
        }
        return name;
    }

    /**
     * Returns the V3 name of the test agent
     * 
     * @param testAgent
     *        am XmlBeans test agent object
     * @return the V3 name
     */
    private Name getV3Name(
            gov.nist.healthcare.configuration.testframework.commons.TestAgent testAgent) {
        Name name = null;
        if (testAgent.getName().getHL7V3ApplicationName() != null) {
            NameV3Attributes app = testAgent.getName().getHL7V3ApplicationName();
            NameV3Attributes fac = testAgent.getName().getHL7V3FacilityName();
            if (fac == null) {
                name = new HL7V3Name(app.getRoot(), null);
            } else {
                name = new HL7V3Name(app.getRoot(), fac.getRoot());
            }
        }
        return name;
    }

    /**
     * Returns the V2 name of the test agent
     * 
     * @param actor
     *        am XmlBeans actor object
     * @return the V2 name
     */
    private Name getV2Name(
            gov.nist.healthcare.configuration.testframework.commons.Actor actor) {
        Name name = null;
        if (actor.getName().getHL7V2ApplicationName() != null) {
            NameV2Attributes app = actor.getName().getHL7V2ApplicationName();
            NameV2Attributes fac = actor.getName().getHL7V2FacilityName();
            name = new HL7V2Name(new HD(app.getNsId(), app.getUid(),
                    app.getUidType()), new HD(fac.getNsId(), fac.getUid(),
                    fac.getUidType()));
        }
        return name;
    }

    /**
     * Returns the V3 name of the test agent
     * 
     * @param actor
     *        am XmlBeans actor object
     * @return the V3 name
     */
    private Name getV3Name(
            gov.nist.healthcare.configuration.testframework.commons.Actor actor) {
        Name name = null;
        if (actor.getName().getHL7V3ApplicationName() != null) {
            NameV3Attributes app = actor.getName().getHL7V3ApplicationName();
            NameV3Attributes fac = actor.getName().getHL7V3FacilityName();
            if (fac == null) {
                name = new HL7V3Name(app.getRoot(), null);
            } else {
                name = new HL7V3Name(app.getRoot(), fac.getRoot());
            }
        }
        return name;
    }

    /**
     * Adds an actor. If the actor name already exists, it will override the ip
     * and port number.
     * 
     * @param name
     *        the name of the actor
     * @param openInetSocketAddress
     *        the ip address and port number of the actor for open connection
     * @param openPath
     *        the path for the open connection
     * @param secureInetSocketAddress
     *        the ip address and port number of the actor for secure connection
     * @param securePath
     *        the path for the secure connection
     * @param override
     *        a boolean to override the ip address and port numbre if an actor
     *        of the same name already exists
     * @return the actor
     */
    private Actor addActor(Name name, InetSocketAddress openInetSocketAddress,
            String openPath, InetSocketAddress secureInetSocketAddress,
            String securePath, boolean override) {
        Actor actor = null;
        if ((!override && !actors.containsKey(name)) || override) {
            actor = new Actor();
            actor.setName(name);
            actor.setSecureInetSocketAddress(secureInetSocketAddress);
            actor.setSecurePath(securePath);
            actor.setOpenInetSocketAddress(openInetSocketAddress);
            actor.setOpenPath(openPath);
            actors.put(name, actor);
        }
        return actor;
    }

    /**
     * Return true if the Name refers to a test agent
     * 
     * @param name
     *        the name to test
     * @return true if the name refers to a test agent
     */
    public boolean isTestAgent(Name name) {
        return testAgents.get(name) != null;
    }

    /**
     * Return true if the Name refers to an actor
     * 
     * @param name
     *        the name to test
     * @return true if the name refers to an actor
     */
    public boolean isActor(Name name) {
        return actors.get(name) != null;
    }

    /**
     * Add an actor.
     * 
     * @param actorConfig
     *        an Actor object from a configuration file
     * @param testAgent
     *        the test agent associated to that actor
     * @param override
     *        a boolean to override the ip address and port numbre if an actor
     *        of the same name already exists
     * @return the actor
     */
    public Actor addActor(
            gov.nist.healthcare.configuration.testframework.commons.Actor actorConfig,
            TestAgent testAgent, boolean override) {
        Actor actor = null;
        Name testAgentName = testAgent.getName();
        List<Name> actors = testAgentActors.get(testAgentName);
        if (actors == null) {
            actors = new ArrayList<Name>();
            testAgentActors.put(testAgentName, actors);
        }
        Name actorName = getV2Name(actorConfig);
        if (actorName == null) {
            actorName = getV3Name(actorConfig);
        }
        Name nameV2 = getV2Name(actorConfig);
        Name nameV3 = getV3Name(actorConfig);
        // Open Host
        InetSocketAddress openInetSocketAddress = null;
        String openPath = null;
        Host openHost = actorConfig.getOpenHost();
        if (openHost != null) {
            String ip = openHost.getIp();
            openPath = openHost.getPath();
            if (ip != null) {
                openInetSocketAddress = new InetSocketAddress(openHost.getIp(),
                        openHost.getPort().intValue());
            }
        }
        // Secure Host
        InetSocketAddress secureInetSocketAddress = null;
        String securePath = null;
        // Security security = null;
        SecureHost secureHost = actorConfig.getSecureHost();
        if (secureHost != null) {
            String ip = secureHost.getIp();
            securePath = secureHost.getPath();
            if (ip != null) {
                secureInetSocketAddress = new InetSocketAddress(
                        secureHost.getIp(), secureHost.getPort().intValue());
            }
            // security = secureHost.getSecurity();
        }
        // actor = addActor(actorName, openInetSocketAddress,
        // secureInetSocketAddress, openPath, securePath, security,
        // override);
        if (!actors.contains(actorName) || override) {
            actors.add(actorName);
        }
        // V2
        if (nameV2 != null) {
            actor = addActor(nameV2, openInetSocketAddress, openPath,
                    secureInetSocketAddress, securePath, override);
        }
        // V3
        if (nameV3 != null) {
            actor = addActor(nameV3, openInetSocketAddress, openPath,
                    secureInetSocketAddress, securePath, override);
        }
        return actor;
    }

    /**
     * Get the event model
     * 
     * @return the event model
     */
    public EventModel getEventModel() {
        return events;
    }

}
